#include "CodeGenerator.h"

void initCodeGenerator()
{
    initSymTabStack();

    registers = malloc(NOOFREG * sizeof(int));
    code = malloc(MAXCODE * sizeof(struct CodeLine));

    registers[0] = 1;
    registers[HEAPREG] = 1;

    int i = 2;
    while(i < NOOFREG)
    {
        registers[i] = 0;
        i = i + 1;
    }

	programCounter = 0;
	SPOffset = 0;
	GPOffset = 0;
	fJumpAddress = 0;
	bJumpAddress = 0;
	branchAddress = 0;

	put(ADDI, HEAPREG, HEAPREG, HEAPSTART);
}

void addObjectToMonster(object_t o)
{
    if(top equ bottom)
    {
        o-> scope = SCOPEGLOBAL;
        o-> val = GPOffset;
        if(o-> oClass equ CLASSVAR)
        {
            incr(GP);
        }

    }
    else
    {
        o-> scope = SCOPELOCAL;
        o-> val = SPOffset;
        if(o-> oClass equ CLASSVAR)
        {
            incr(SP);
        }
    }
    newObj(o);
}

item_t getConstItem(int value)
{
    item_t constItem = newItem;
    constItem-> mode = MODECONST;
    constItem-> type = intType;
    constItem-> value = value;
    return constItem;
}

item_t getVarItem(object_t o)
{
    if(o neq NULL)
    {
        item_t varItem = newItem;
        varItem-> mode = MODEVAR;
        varItem-> type = o-> type;
        if(o-> scope equ SCOPEGLOBAL)
        {
            varItem-> reg = GP;
        }
        elif (o-> scope equ SCOPELOCAL)
        {
            varItem-> reg = SP;
        }
        varItem-> offset = o-> val;
        return varItem;
    }
    mark("Given Object is NULL, can't create variable Item", 1);
    return NULL;
}

item_t mallocItem(type_t type, item_t sizeItem)
{
    if(type neq NULL or sizeItem equ NULL)
    {
        item_t item = newItem;
        item-> mode = MODEREG;
        item-> type = type;
        item-> reg = getRegister();
        put(ADD, item-> reg, 0, HEAPREG);
        incrHeap(type-> size, sizeItem);
        return item;
    }
    mark("Error type or sizeItem is NULL", 1);
    return NULL;
}

void assignBinaryOperator(item_t left, item_t right)
{
    if(left-> type neq left-> type)
    {
        mark("Incompatible types", 1);
    }

    if(right-> type equ boolType)
    {
        unloadBool(right);
    }
    load(right);

    // leftItem must be in VAR_MODE or REF_MODE
    // rightItem must be in REG_MODE
    put(STW, right-> reg, left-> reg, left-> offset);

    if(left-> mode equ MODEREF)
    {
        freeReg(left-> reg);
    }
    freeReg(right-> reg);
}

void factorOperator(item_t item)
{
    if(item-> type equ boolType)
    {
        loadBool(item);
        int tmp = item-> fls;
        item-> fls = item-> tru;
        item-> op = negateBranch(item-> op);
    }
    else
    {
        mark("boolean expression expected", 1);
    }
}


int concatenate(int one, int two)
{
    int three, four, op, a, b;

    if(one != 0)
    {
        three = one;
        do
        {
            //four = decodeC(three);
            decode(&three, &op, &a, &b, &four);
            three = four;
        }
        while(four != 0);
        code[three] = code[three] - four + two;
        return one;
    }
    else
    {
        return two;
    }
}

void simpleExpressionOR(item_t item)
{
    if(item-> type equ boolType)
    {
        loadBool(item);
        printf("Item operator %d\n", item-> op);
        put(branch(0, item-> op), item-> reg, 0, item-> tru);
        freeReg(item-> reg);
        item-> tru = programCounter - 1;
        fixLink(item-> fls);
        item-> fls = 0;
    }
    else
    {
        mark("boolean expression expected", 1);
    }
}

void simpleExpressionAND(item_t item)
{
    if(item-> type equ boolType)
    {
        loadBool(item);
        printf("Item operator %d\n", item-> op);
        put(branch(1, item-> op), item-> reg, 0, item-> tru);
        freeReg(item-> reg);
        item-> tru = programCounter - 1;
        fixLink(item-> fls);
        item-> fls = 0;
    }
    else
    {
        mark("boolean expression expected", 1);
    }
}

int branch(int or_and, int op)
{
    if(or_and equ 0)    /* if 'or' */
    {
        return op;
    }
    else                /* if 'and' */
    {
        return negateBranch(op);
    }
}


void arithBinaryOperator(int operator, item_t left, item_t right)
{
    if(left equ NULL or right equ NULL)
    {
        mark("Item is NULL mayby that's my fault because the factor is not implemented", 1);
        return;
    }

    if(operator equ OR)
    {
        simpleExpressionOR(left);
        if(left-> type equ boolType and
           right-> type equ boolType)
        {
            loadBool(right);
            left-> reg = right-> reg;
            left-> fls = right-> fls;
            left-> tru = concatenate(right-> tru, left-> tru);
            left-> op = right-> op;
        }
        else
        {
            mark("boolean expression expected!", 1);
        }
    }
    elif(operator equ AND)
    {
        simpleExpressionAND(left);
        if(left-> type equ boolType and
           right-> type equ boolType)
        {
            loadBool(right);
            left-> reg = right-> reg;
            left-> fls = concatenate(right-> fls, left-> fls);
            left-> tru = right-> tru;
            left-> op = right-> op;
        }
        else
        {
            mark("boolean expression expected!", 1);
        }
    }
    elif(left-> type-> form equ FORMINT and
         right-> type-> form equ FORMINT)
    {
        if(right-> mode equ MODECONST)
        {
            if(left-> mode equ MODECONST)
            {
                if(operator equ PLUS)
                {
                    left-> value = left-> value + right-> value;
                }
                elif (operator equ MINUS)
                {
                    left-> value = left-> value - right-> value;
                }
                elif (operator equ TIMES)
                {
                    left-> value = left-> value * right-> value;
                }
                elif (operator equ DIV)
                {
                    left-> value = left-> value / right-> value;
                }
                elif (operator equ MOD)
                {
                    left-> value = left-> value % right-> value;
                }
            }
            else
            {
                load(left);
                if(operator equ PLUS)
                {
                    put(ADDI, left-> reg, left-> reg, right-> value);
                }
                elif(operator equ MINUS)
                {
                    put(SUBI, left-> reg, left-> reg, right-> value);
                }
                elif (operator equ TIMES)
                {
                    put(MULI, left-> reg, left-> reg, right-> value);
                }
                elif (operator equ DIV)
                {
                    put(DIVI, left-> reg, left-> reg, right-> value);
                }
                elif (operator equ MOD)
                {
                    put(MODI, left-> reg, left-> reg, right-> value);
                }
            }
        }
        else
        {
            load(left);
            load(right);
            if(operator equ PLUS)
            {
                put(ADD, left-> reg, left-> reg, right-> reg);
            }
            elif(operator equ MINUS)
            {
                put(SUB, left-> reg, left-> reg, right-> reg);
            }
            elif (operator equ TIMES)
            {
                put(MUL, left-> reg, left-> reg, right-> reg);
            }
            elif (operator equ DIV)
            {
                put(Div, left-> reg, left-> reg, right-> reg);
            }
            elif (operator equ MOD)
            {
                put(Mod, left-> reg, left-> reg, right-> reg);
            }
            freeReg(right-> reg);
        }
    }
    else
    {
        mark("Integer expression expected", 1);
    }
}

void incrHeap(int size, item_t sizeItem)
{
    arithBinaryOperator(TIMES, sizeItem, getConstItem(size));
    if(sizeItem-> mode equ MODECONST)
    {
        put(ADDI, HEAPREG, HEAPREG, sizeItem-> value);
    }
    elif(sizeItem-> mode equ MODEREG)
    {
        put(ADD, HEAPREG, HEAPREG, sizeItem-> reg);
    }
    else
    {
        mark("Don't know what to do with this mode", 1);
    }
}

void field(object_t object, item_t item)
{
    load(item);
    item-> mode = MODEREF;
    item-> offset = object-> val;
    item-> type = object-> type;
}

void indexer(item_t indexItem, item_t item)
{
    if(indexItem-> mode equ MODECONST)
    {
        load(item);
        item-> mode = MODEREF;
        item-> offset = indexItem-> value * 4;
    }
    else
    {
        load(indexItem);
        put(MULI, indexItem-> reg, indexItem-> reg, 4);
        load(item);
        item-> mode = MODEREF;
        put(ADD, item-> reg, item-> reg, indexItem-> reg);
        freeReg(indexItem-> reg);
    }
    item-> type = item-> type-> base;
}

void put(int op, int one, int two, int three)
{
    codeline_t codeline = newCodeLine;
    codeline-> op = op;
    codeline-> one = one;
    codeline-> two = two;
    codeline-> three = three;
    code[programCounter] = codeline;
    incr(PC);
}

void load(item_t item)
{
    if(item-> mode equ MODECONST)
    {
        printf("AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_reg=%d\n",item->reg);
        printItem(item);
        printf("AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_reg=%d\n",item->reg);
        constToReg(item);
        printf("AXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_reg=%d\n",item->reg);
    }
    elif(item-> mode equ MODEVAR)
    {
        varToReg(item);
    }
    elif(item-> mode equ MODEREF)
    {
        refToReg(item);
    }
}

void constToReg(item_t item)
{
    item-> mode = MODEREG;
    item-> reg = getRegister();
    put(ADDI, item-> reg, 0, item-> value);
    item-> value = 0;
    item-> offset = 0;
}

void varToReg(item_t item)
{
    item-> mode = MODEREG;
    int newReg = getRegister();
    put(LDW, newReg, item-> reg, item-> offset);
    item-> reg = newReg;
    item-> offset = 0;
}

void refToReg(item_t item)
{
    item-> mode = MODEREG;
    put(LDW, item-> reg, item-> reg, item-> offset);
    item-> offset = 0;
}

string_t getInstruction(int key)
{
    if(ADD equ key)
        return "ADD";
    elif(SUB equ key)
        return "SUB";
    elif(MUL equ key)
        return "MUL";
    elif(Div equ key)
        return "DIV";
    elif(Mod equ key)
        return "MOD";
    elif(CMP equ key)
        return "CMP";
    elif(ADDI equ key)
        return "ADDI";
    elif(SUBI equ key)
        return "SUBI";
    elif(MULI equ key)
        return "MULI";
    elif(DIVI equ key)
        return "DIVI";
    elif(MODI equ key)
        return "MODI";
    elif(CMPI equ key)
        return "CMPI";
    elif(LDW equ key)
        return "LDW";
    elif(POP equ key)
        return "POP";
    elif(STW equ key)
        return "STW";
    elif(PSH equ key)
        return "PSH";
    elif(BEQ equ key)
        return "BEQ";
    elif(BNE equ key)
        return "BNE ";
    elif(BLT equ key)
        return "BLT";
    elif(BGE equ key)
        return "BGE";
    elif(BLE equ key)
        return "BLE";
    elif(BGT equ key)
        return "BGT";
    elif(BR equ key)
        return "BR";
    printf("Unkn instruction!\n");

    return NULL;
}


int getRegister()
{
    int index = 1;
    while(index < NOOFREG)
    {
        if(registers[index] equ 0)
        {
            registers[index] = 1;
            return index;
        }
        index = index + 1;
    }
    mark("All Registers in use", 1);

    return -1;
}

void freeReg(int reg)
{
    if(reg < 0)
    {
        mark("register negativ", 1);
    }
	elif(reg >= NOOFREG)
	{
		mark("register too high", 1);
	}
	else
	{
		registers[reg] = 0;
	}
}

void run()
{
    FILE ptr outPutFile = fopen ("outPut.eas", "w");
	if(outPutFile equ NULL)
	{
	    printf ("Can not open file: outPut.eas\n");
		exit(1);
	}
	printf("\n\n");
    int i = 0;
    unsigned long instruction = 0;
    //encode(&instruction, ADDI, 28, 0, 1024);    // this is pretty bad ...
    //fwrite(&instruction, sizeof(instruction), 1, outPutFile);
    while(i < programCounter)
    {
        printf(             "%s %d %d %d\n", getInstruction(code[i]-> op), code[i]-> one, code[i]-> two, code[i]-> three);
        //fprintf(outPutFile, "%s %d %d %d\n", getInstruction(code[i]-> op), code[i]-> one, code[i]-> two, code[i]-> three);
        instruction = 0;
        encode(&instruction, code[i]-> op, code[i]-> one, code[i]-> two, code[i]-> three);
        fwrite(&instruction, sizeof(instruction), 1, outPutFile);
        i++;
    }
    encode(&instruction, TRAP, 0, 0, 0);   // ok
    fwrite(&instruction, sizeof(instruction), 1, outPutFile);
    printf("\n\n");
    fclose(outPutFile);
}

void encode(unsigned long *result, int op, char a, char b, int c) {
    *result = op & 0x3F;
    if ( op >= 1 && op <= 33 ) {        // if Format 0 or Format 1
        *result <<= 5;
        *result |= a & 0x1F;
        *result <<= 5;
        *result |= b & 0x1F;

        if ( op >= 1 && op <= 22 ) {   // if Format 0
            if ( c < 0 ) {
                *result <<= 16;
                *result |= ( 65536 + c ) & 0xFFFF;
            } else {
                *result <<= 16;
                *result |= c & 0xFFFF;
            }
        }
        else {  // if Format 1
            *result <<= 16;
            *result |= c & 0x1F;
    }   }
    else {      // if Format 2
        *result <<= 26;
        *result |= c & 0x3FFFFFF;
    }
}

int decode(unsigned long *instruction, char *op, char *a, char *b, int *c) {

    *op = ( *instruction >> 26 ) & 0x3F;

    if ( *op >= 1 && *op <= 33 ) {    // if Format 0 or Format 1
        *a = ( *instruction >> 21 ) & 0x1F;
        *b = ( *instruction >> 16 ) & 0x1F;

        if ( *op >= 1 && *op <= 22 ) {   // if Format 0
            *c = *instruction & 0xFFFF;
            if ( *c >= 32768 ) {
                *c -= 65536;
            }
        }
        else {  // if Format 1
            *c = *instruction & 0x1F;
        }
        return 1;
    }
    else if ( *op == 34 ) {   // if Format 2
        *c = *instruction & 0x3FFFFFF;
        return 1;
    }
    else {
        return 0;
    }
}

void printItem(item_t item)
{
    if(item neq NULL)
    {
        printf("mode: %d\n", item-> mode);
        printType(item-> type);
        printf("reg: %d\n", item-> reg);
        printf("offset: %d\n", item-> offset);
        printf("value: %d\n", item-> value);
    }
    else
    {
        mark("item is NULL", 1);
    }
}

void incr(int pointer)
{
    if(pointer equ SP)
    {
        SPOffset = SPOffset - 4;
    }
    elif(pointer equ GP)
    {
        GPOffset = GPOffset + 4;
    }
    elif(pointer equ PC)
    {
        programCounter = programCounter + 1;
    }
}

void expressionOperator(item_t left, item_t right, int operator)
{
    if(left-> type equ NULL or right-> type equ NULL)
    {
        mark("Type is NULL, maybe factor is not supported yet", 1);
    }
    if((left-> type equ intType) and
       (right-> type equ intType))
    {
        load(left);
        if((right-> mode neq MODECONST) or
           (right-> value neq 0))
        {
            load(right);
            put(CMP, left-> reg, left-> reg, right-> reg);
            freeReg(right-> reg);
        }
        left-> mode = MODECOND;
        left-> type = boolType;
        left-> op = operator;
        left-> fls = 0;
        left-> tru = 0;
    }
    else
    {
        mark("Integer expression expected(expressionOperator)", 1);
    }
}

int forwardJump() {
    put(BR, 0, 0, 0);
    return programCounter - 1;
}

void conditionalJump(item_t item)
{
    put(negateBranch(item-> op), item->reg, 0, item->fls);
    freeReg(item->reg);
    item->fls = programCounter - 1;
}

int negateBranch(int op)
{
    switch(op)
    {
        case BEQ:
            return BNE;
        case BGE:
            return BLT;
        case BGT:
            return BLE;
        case BLE:
            return BGT;
        case BLT:
            return BGE;
        case BNE:
            return BEQ;
        default:
            return -1;
    }
}

void fixUp(int branchAddress)
{
    code[branchAddress]-> three = programCounter - branchAddress;
}

void fixLink(int branchAddress)
{
    int nextBranchAddress;

    while(branchAddress neq 0)
    {
        nextBranchAddress = code[branchAddress]-> three;
        fixUp(branchAddress);
        branchAddress = nextBranchAddress;
    }
}

void unloadBool(item_t item)
{
    if(item-> mode equ MODECOND)
    {
        conditionalJump(item);
    }
    fixLink(item-> tru);
    item-> mode = MODEREG;
    put(ADDI, item-> reg, 0, 1);
    put(BR, 0, 0, 2);

    fixLink(item-> fls);

    put(ADDI, item-> reg, 0, 0);
}

void bJump(int backAddress)
{
    put(BR, 0, 0, backAddress - programCounter);
}

void loadBool(item_t item)
{
    if(item-> mode neq MODECOND)
    {
        load(item);
        item-> mode = MODECOND;
        //the operator is set already!
        item-> op = BNE;
        item-> fls = 0;
        item-> tru = 0;
    }
}

void prologue(int localSize)
{
    put(PSH, LINK, SP, 4);
    put(PSH, FP, SP, 4);
    put(ADD, FP, 0, SP);
    put(SUBI, SP, SP, localSize);
}

void epilogue(int paramSize)
{
    put(ADD, SP, 0, FP);
    put(POP, FP, SP, 4);
    put(POP, LINK, SP, paramSize + 4);
    put(RET, 0, 0, LINK);
}

void procedureReturn() {
    //struct item_t* item;

    if(curKey-> sym equ RETURN)
    {
        get();
    }
    else
    {
        mark("return statement expected", 1);
    }

    if ((curKey-> sym equ PLUS) or (curKey-> sym equ MINUS) or
        (curKey-> sym equ IDENT) or (curKey-> sym equ NUMBER) or
        (curKey-> sym equ LPAREN) or (curKey-> sym equ NEG) or (curKey-> sym equ STRING))
    {
        //item = malloc(sizeof(struct item_t));
        item_t item = newItem;
        expression(item);

        if (item->type != procedureContext->type)
        {
            mark("return type mismatch", 1);
        }

        if (item->type == BOOL_TYPE)
        {
            unloadBool(item);
        }

        load(item);
        // assumes: reg[0]==0 for MOV semantics
        put(ADD, RR, 0, item->reg);
        releaseRegister(item->reg);
    }
    returnFJumpAddress = fJumpChain(returnFJumpAddress);
}

